/**
 * 泛型是在保证类型安全的前提下，让函数与多种类型一起工作，从而实现复用，
 * 常用与函数，接口，类，当中
 * 泛型在保证类型安全（不丢失类型信息）的同时，
 * 可以让函数等与多种不同的类型一起工作，灵活可复用
 */

/**
 * 泛型函数
 * 语法：
 * 1.在函数名称id后面加一对尖括号<>，尖括号中天极爱类型变量，比如这里的Type
 * 2.类型变量Type,他是一种特殊类型的变量，它处理类型而不是值
 * 3.这个类型变量Type更像是一个类型的容器，他用来捕获用户提供的类型
 * 4.具体是什么类型，是在用户调用该函数的时候传入的
 * 5.因为Type是一个类型，所以可以把它当做函数的参数和返回值的类型
 * 6.表示参数和返回值具有相同的类型
 */
function id<Type> (value: Type): Type {
  return value
}

// 泛型函数的使用
// 直接在函数名称后面的尖括号中指定具体的类型
// 然后在后面的小括号中传入具体的变量就可以
const num = id<number>(10)
// console.log(num)

const str = id<string>('abc')
// console.log(str)

// 泛型函数的调用是可以简化的，可以省略<类型>
// 因为在传入实际参数的时候，ts具有自动类型推断的功能
const str2 = id('tom')
// console.log(str2)

/**
 * 泛型约束，因为泛型什么都可以表示，所以它不能访问任何的属性
 * 这时就需要为泛型天极爱约束来收缩类型
 * 主要方式有
 * 1.指定更加具体的类型，比如指定为数组类型
 * 2.添加约束
 *  2.1通过extends关键字使用该接口，为泛型添加约束
 *  2.2该约束表示传入的类型必须具有length属性
 */
interface ILength {
  length: number
}
function id2<Type extends ILength> (value: Type): Type {
  console.log(value.length)
  return value
}
// console.log(id2('123'))

/**
 * 泛型的类型变量可以有多个，并且类型变量之间还可以约束
 * 比如第二个类型变量受到第一个类型变量的约束
 * keyof 关键字接收一个对象类型，生成其键名称（可能是字符串或数字）的联合类型。
 */
function getProp<Type, Key extends keyof Type> (obj: Type, key: Key) {
  return obj[key]
}
let person = { name: 'jack', age: 18 }
// console.log(getProp(person, 'name'))

/**
 * 泛型接口
 */
interface IdFunc<Type> {
  id: (val: Type) => Type
  ids: () => Type[]
}

let obj: IdFunc<number> = {
  id (val) {
    return val
  },
  ids () {
    return [1, 3, 5]
  }
}

/**
 * 泛型工具类
 * 1.Partial<Type>用来构建一个类型，将type的所有属性设置为可选
 * 2.Readonly<Type>用来构建一个类型，将type的所有属性均设置为只读
 * 3.Pick<type,keys>从type中选择一组属性来构造新类型
 * 4.Record<keys,type>构造一个对象类型，属性键为keys,属性类型为type
 */